package com.g.ocache.impl;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.g.ocache.Cache;
import com.g.ocache.CacheInitializer;
import com.g.ocache.CacheManager;
import com.g.ocache.event.CacheEvent;
import com.g.ocache.event.CacheEventPublisher;
import com.g.ocache.event.CacheEventType;
import com.g.ocache.impl.cache.DefaultCache.DefaultCacheFactory;
import com.g.ocache.utils.CallableExecutor;


/**
 * 
 * @author Jingqi Xu
 */
public final class BasicCacheManager<K, V> implements CacheManager<K, V> {
	//
	private static final Logger LOGGER = LoggerFactory.getLogger(BasicCacheManager.class);
	
	//
	private final CallableExecutor<Cache<K, V>> initializer;
	private final ConcurrentHashMap<Object, Cache<K, V>> caches;
	
	/**
	 * 
	 */
	public BasicCacheManager() {
		this.caches = new ConcurrentHashMap<Object, Cache<K, V>>();
		this.initializer = new CallableExecutor<Cache<K, V>>(false);
	}

	/**
	 * 
	 */
	public void clear(boolean stop) {
		//
		if(!stop) {
			this.caches.clear();
		} else {
			//
			List<Cache<K, V>> list = getAllCaches();
			this.caches.clear();
			
			//
			for(Cache<K, V> cache : list) {
				try {
					cache.stop();
				} catch (Exception e) {
					LOGGER.error("failed to stop cache: " + cache.getName(), e);
				}
			}
		}
	}

	public List<Cache<K, V>> getAllCaches() {
		List<Cache<K, V>> r = new LinkedList<Cache<K, V>>();
		r.addAll(caches.values());
		return r;
	}

	public Cache<K, V> getCache(Object name) throws Exception {
		return getCache(name, new DefaultCacheFactory<K, V>());
	}

	public Cache<K, V> getCache(Object name, Cache.Factory<K, V> factory) throws Exception {
		Cache<K, V> r = this.caches.get(name);
		if(r == null) {
			r = initialize(name, factory);
		}
		return r;
	}

	/**
	 * 
	 */
	private Cache<K, V> initialize(Object name, Cache.Factory<K, V> factory) throws Exception {
		return initializer.execute(name, new CacheCallable(name, factory));
	}
	
	/**
	 * 
	 */
	private class CacheCallable implements Callable<Cache<K, V>> {
		//
		private final Object name;
		private final Cache.Factory<K, V> factory;
		
		/**
		 * 
		 */
		public CacheCallable(Object name, Cache.Factory<K, V> factory) {
			this.name = name;
			this.factory = factory;
		}

		/**
		 * 
		 */
		public Cache<K, V> call() throws Exception {
			// Create
			Cache<K, V> cache = factory.create(name);
			
			// Start
			cache.start();
			
			// Initialize
			final CacheEvent<K, V> event = new CacheEvent<K, V>(CacheEventType.initialize);
			event.setCache(cache);
			try {
				CacheInitializer<K, V> initializer = cache.getInitializer();
				if(initializer != null) {
					final Map<K, V> values = initializer.initialize(name);
					cache.putAll(values);
				}				
			} catch(Exception e) {
				event.setException(e);
				throw e;
			} finally {
				CacheEventPublisher<K, V>  publisher = cache.getPublisher();
				if (publisher != null) {
					publisher.publish(event);
				}
			}
			
			// 
			caches.putIfAbsent(name, cache);
			return cache;
		}
	}
}
